package com.gitee.zou8944.ipersist.core

import com.gitee.zou8944.ipersist.core.bean.Configuration
import com.gitee.zou8944.ipersist.exception.NoSqlStatementException
import com.gitee.zou8944.ipersist.utils.Executor
import java.lang.reflect.Proxy
import java.sql.Connection

@Suppress("UNCHECKED_CAST")
class DefaultSqlSession(
    private val connection: Connection,
    private val configuration: Configuration
) : SqlSession {

    override fun <R> selectOne(statementId: String, entity: Any?): R? {
        val executeResult = execute(statementId, entity)
        return (executeResult as List<Any>).firstOrNull() as R?
    }

    override fun <R> selectList(statementId: String, entity: Any?): List<R> {
        val executeResult = execute(statementId, entity)
        return (executeResult as List<Any>).map { it as R }
    }

    override fun updateOne(statementId: String, entity: Any?): Int {
        return execute(statementId, entity) as Int
    }

    override fun insertOne(statementId: String, entity: Any?): Int {
        return execute(statementId, entity) as Int
    }

    override fun deleteOne(statementId: String, entity: Any?): Int {
        return execute(statementId, entity) as Int
    }

    override fun <T> getMapper(clazz: Class<T>): T {
        return Proxy.newProxyInstance(clazz.classLoader, arrayOf(clazz)) { _, method, args ->
            val statementId = "${clazz.canonicalName}.${method.name}"
            // FIXME 暂时只考虑参数仅为一个实体类的情况
            val executeResult = execute(statementId, args?.firstOrNull())
            // FIXME 根据方法的返回值决定转换的类型，也是不严谨的
            when (method.returnType) {
                List::class.java -> (executeResult as List<Any>)
                Int::class.java -> executeResult as Int
                else -> (executeResult as List<Any>).firstOrNull()
            }
        } as T
    }

    override fun close() {
        connection.close()
    }

    private fun execute(statementId: String, entity: Any?): Any {
        val sqlStatement = configuration.mappedStatement[statementId] ?: throw NoSqlStatementException(statementId)
        return Executor.execute(connection, sqlStatement, entity)
    }
}